﻿#region License

// Copyright (c) 2013, ClearCanvas Inc.
// All rights reserved.
// http://www.clearcanvas.ca
//
// This file is part of the ClearCanvas RIS/PACS open source project.
//
// The ClearCanvas RIS/PACS open source project is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// The ClearCanvas RIS/PACS open source project is distributed in the hope that it
// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along with
// the ClearCanvas RIS/PACS open source project.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Resources;
using System.Xml;

namespace ClearCanvas.Dicom.DataDictionaryGenerator
{
	public class CodeGenerator
	{
		private readonly SortedList<uint, Tag> _tagList = null;
		private readonly SortedList _tSyntaxList = null;
		private readonly SortedList _sopList = null;
		private readonly SortedList _metaSopList = null;
		private readonly XmlDocument _transferSyntaxDoc = null;
		//private readonly XmlDocument _sopClassDoc;`

		public CodeGenerator(SortedList<uint, Tag> tags, SortedList tSyntax, SortedList sops, SortedList metaSops, XmlDocument transferSyntaxDoc/*, XmlDocument sopClassDoc*/)
		{
			_tagList = tags;
			_tSyntaxList = tSyntax;
			_sopList = sops;
			_metaSopList = metaSops;
			_transferSyntaxDoc = transferSyntaxDoc;
			//_sopClassDoc = sopClassDoc;
		}

		private void WriterHeader(StreamWriter writer)
		{
			var assembly = typeof (CodeGenerator).Assembly;
			var resourceName = assembly.GetManifestResourceNames().First(n => n.Contains("License.txt"));
			string license = null;

			using (var stream = assembly.GetManifestResourceStream(resourceName))
			{
				using (var reader = new StreamReader(stream))
				{
					license = reader.ReadToEnd();
				}
			}
			license = license.Replace("$year$", DateTime.Now.Year.ToString(CultureInfo.InvariantCulture));
			writer.WriteLine(license);
			writer.WriteLine("");
			writer.WriteLine("using System;");
			writer.WriteLine("using System.Collections.Generic;");
			writer.WriteLine("using System.Linq;");
			writer.WriteLine("using ClearCanvas.Common;"); 
			writer.WriteLine("");
			writer.WriteLine("// This file is auto-generated by the ClearCanvas.Dicom.DataDictionaryGenerator project.");
			writer.WriteLine("");
			writer.WriteLine("namespace ClearCanvas.Dicom");
			writer.WriteLine("{");
		}

		private void WriterFooter(StreamWriter writer)
		{
			writer.WriteLine("}");
		}

		/// <summary>
		/// Create the DicomTags.cs file.
		/// </summary>
		/// <param name="tagFile"></param>
		public void WriteTags(String tagFile)
		{
			var writer = new StreamWriter(tagFile);

			WriterHeader(writer);

			writer.WriteLine("    /// <summary>");
			writer.WriteLine("    /// This structure contains defines for all DICOM tags.");
			writer.WriteLine("    /// </summary>");
			writer.WriteLine("    public struct DicomTags");
			writer.WriteLine("    {");

			IEnumerator<Tag> iter = _tagList.Values.GetEnumerator();

			while (iter.MoveNext())
			{
				Tag tag = iter.Current;

				writer.WriteLine("        /// <summary>");
				writer.WriteLine("        /// <para>" + tag.tag + " " + tag.unEscapedName + "</para>");
				writer.WriteLine("        /// <para> VR: " + tag.vr + " VM:" + tag.vm + "</para>");
				if (tag.retired != null && tag.retired.Equals("RET"))
					writer.WriteLine("        /// <para>This tag has been retired.</para>");
				writer.WriteLine("        /// </summary>");
				writer.WriteLine("        public const uint " + tag.varName + " = " + tag.nTag + ";");
			}

			writer.WriteLine("    }");
			WriterFooter(writer);

			writer.Close();
		}

		/// <summary>
		/// Get transfer syntax details from the TransferSyntax.xml file for a specific transfer syntax.
		/// </summary>
		/// <param name="uid"></param>
		/// <param name="littleEndian"></param>
		/// <param name="encapsulated"></param>
		/// <param name="explicitVR"></param>
		/// <param name="deflated"></param>
		/// <param name="lossy"></param>
		/// <param name="lossless"></param>
		public void GetTransferSyntaxDetails(String uid, ref String littleEndian, ref String encapsulated,
		                                     ref String explicitVR, ref String deflated, ref string lossy, ref string lossless)
		{
			XmlNode syntaxNode = _transferSyntaxDoc.FirstChild;

			// I know the format, just do a quick traversal to the first TransferSyntax entry
			syntaxNode = syntaxNode.NextSibling;
			syntaxNode = syntaxNode.FirstChild;

			while (syntaxNode != null)
			{
				if (syntaxNode.Name.Equals("TransferSyntax"))
				{
					String xmlUid = syntaxNode.Attributes["uid"].Value;

					if (xmlUid.Equals(uid))
					{
						littleEndian = syntaxNode.Attributes["littleEndian"].Value;
						encapsulated = syntaxNode.Attributes["encapsulated"].Value;
						explicitVR = syntaxNode.Attributes["explicitVR"].Value;
						deflated = syntaxNode.Attributes["deflated"].Value;
						lossy = syntaxNode.Attributes["lossy"].Value;
						lossless = syntaxNode.Attributes["lossless"].Value;
						return;
					}
				}
				syntaxNode = syntaxNode.NextSibling;
			}
		}

		public void GetSopClassDetails(SopClass sopClass, out string category)
		{
			//I started out using an xml document to store this information, but it wasn't worth it.
			//As it turns out, there's a fairly reliable formula based on SOP class names.
			if (sopClass.IsImage)
				category = "SopClassCategory.Image";
			else if (sopClass.IsStorage)
				category = "SopClassCategory.Storage";
			else
				category = "SopClassCategory.Uncategorized";
			
			/*
			XmlNode sopClassNode = _sopClassDoc.FirstChild;

			sopClassNode = sopClassNode.NextSibling;
			sopClassNode = sopClassNode.FirstChild;

			while (sopClassNode != null)
			{
				if (sopClassNode.Name.Equals("SopClass"))
				{
					String xmlUid = sopClassNode.Attributes["uid"].Value;

					if (xmlUid.Equals(uid))
					{
						isImage = sopClassNode.Attributes["isImage"].Value;
						return;
					}
				}
				sopClassNode = sopClassNode.NextSibling;
			}

			if (name.ToLower().Contains("Image Storage"))
				isImage = "true";
		*/
		}

		/// <summary>
		/// Create the TransferSyntax.cs file.
		/// </summary>
		/// <param name="syntaxFile"></param>
		public void WriteTransferSyntaxes(String syntaxFile)
		{
			var writer = new StreamWriter(syntaxFile);

			WriterHeader(writer);

            writer.WriteLine("    partial class TransferSyntax");
            writer.WriteLine("    {");

			IEnumerator iter = _tSyntaxList.GetEnumerator();

			while (iter.MoveNext())
			{
				var tSyntax = (SopClass) ((DictionaryEntry) iter.Current).Value;

				writer.WriteLine("        /// <summary>String representing");
				writer.WriteLine("        /// <para>" + tSyntax.Name + "</para>");
				writer.WriteLine("        /// <para>UID: " + tSyntax.Uid + "</para>");
				writer.WriteLine("        /// </summary>");
				writer.WriteLine("        public static readonly String " + tSyntax.VarName + "Uid = \"" + tSyntax.Uid + "\";");
				writer.WriteLine("");

				String littleEndian = "";
				String encapsulated = "";
				String explicitVR = "";
				String deflated = "";
				String lossless = "";
				string lossy = "";

				GetTransferSyntaxDetails(tSyntax.Uid, ref littleEndian, ref encapsulated, ref explicitVR, ref deflated, ref lossy, ref lossless);

				writer.WriteLine("        /// <summary>TransferSyntax object representing");
				writer.WriteLine("        /// <para>" + tSyntax.Name + "</para>");
				writer.WriteLine("        /// <para>UID: " + tSyntax.Uid + "</para>");
				writer.WriteLine("        /// </summary>");
				writer.WriteLine("        public static readonly TransferSyntax " + tSyntax.VarName + " =");
				writer.WriteLine("                    new TransferSyntax(\"" + tSyntax.Name + "\",");
				writer.WriteLine("                                 " + tSyntax.VarName + "Uid,");
				writer.WriteLine("                                 " + littleEndian + ", // Little Endian?");
				writer.WriteLine("                                 " + encapsulated + ", // Encapsulated?");
				writer.WriteLine("                                 " + explicitVR + ", // Explicit VR?");
				writer.WriteLine("                                 " + deflated + ", // Deflated?");
				writer.WriteLine("                                 " + lossy + ", // lossy?");
				writer.WriteLine("                                 " + lossless + " // lossless?");
				writer.WriteLine("                                 );");
				writer.WriteLine("");
			}

			writer.WriteLine("        static TransferSyntax()");
			writer.WriteLine("        {");

			iter = _tSyntaxList.GetEnumerator();

			while (iter.MoveNext())
			{
				var tSyntax = (SopClass) ((DictionaryEntry) iter.Current).Value;

				writer.WriteLine("            _transferSyntaxes.Add(" + tSyntax.VarName + "Uid,");
				writer.WriteLine("                                  " + tSyntax.VarName + ");");
				writer.WriteLine("");
			}

			writer.WriteLine("        }");

			writer.WriteLine("    }");
			WriterFooter(writer);

			writer.Close();
		}

		/// <summary>
		/// Create the SopClass.cs file.
		/// </summary>
		/// <param name="sopsFile"></param>
		public void WriteSqlInsert(String sopsFile)
		{
			var writer = new StreamWriter(sopsFile);

			WriterHeader(writer);

			IEnumerator iter = _sopList.GetEnumerator();

			while (iter.MoveNext())
			{
				var sopClass = (SopClass) ((DictionaryEntry) iter.Current).Value;

				writer.WriteLine("INSERT INTO [ImageServer].[dbo].[SopClass] ([GUID],[SopClassUid],[Description],[NonImage])");
				if (sopClass.Name.ToLower().Contains("image"))
					writer.WriteLine("VALUES (newid(), '" + sopClass.Uid + "', '" + sopClass.Name + "', 0);");
				else
					writer.WriteLine("VALUES (newid(), '" + sopClass.Uid + "', '" + sopClass.Name + "', 1);");
				writer.WriteLine("");

			}


			writer.Close();
		}

		public void WriteTagsText(String tagsFile)
		{
			var writer = new StreamWriter(tagsFile);

			writer.WriteLine("# Copyright (c) 2012, ClearCanvas Inc.");
			writer.WriteLine("# All rights reserved.");
			writer.WriteLine("# http://www.clearcanvas.ca");
			writer.WriteLine("#");
			writer.WriteLine("# generated from DICOM standard");
			writer.WriteLine("# columns are in the same order as parameters to the DicomTag constructor");
			writer.WriteLine("# eg. tag;name;varName;vr;isMultiVrTag;vmLow;vmHigh;isRetired");
			writer.WriteLine("#");

			IEnumerator<Tag> iter = _tagList.Values.GetEnumerator();

			while (iter.MoveNext())
			{
				Tag tag = iter.Current;
				uint vmLow = 0;
				uint vmHigh = 0;
				var charSeparators = new[] {'�', '-'};

				String[] nodes = tag.vm.Split(charSeparators, StringSplitOptions.RemoveEmptyEntries);
				if (nodes.Length == 1)
				{
					vmLow = uint.Parse(nodes[0]);
					vmHigh = vmLow;
				}
				else if (nodes.Length == 2)
				{
					if (nodes[0].Contains("N") || nodes[0].Contains("n"))
					{
						vmLow = 1;
					}
					else
					{
						vmLow = uint.Parse(nodes[0]);
					}
					if (nodes[1].Contains("N") || nodes[1].Contains("n"))
					{
						vmHigh = UInt32.MaxValue;
					}
					else
					{
						vmHigh = uint.Parse(nodes[1]);
					}
				}
				string vr;
				bool isMultiVrTag;
				if (tag.vr.Contains("or"))
				{
					if (tag.varName.Equals("PixelData"))
					{
						vr = "OW";
						isMultiVrTag = true;
					}
					else
					{
						// Just take the first VR listed
						vr = tag.vr.Substring(0, 2);
						isMultiVrTag = true;
					}
				}
				else
				{
					vr = tag.vr;
					isMultiVrTag = false;
				}
				writer.WriteLine("{0};{1};{2};{3};{4};{5};{6};{7}",
				                 tag.nTag, tag.unEscapedName, tag.varName, vr, isMultiVrTag, vmLow, vmHigh, !string.IsNullOrEmpty(tag.retired));
			}

			writer.Close();
		}

		/// <summary>
		/// Create the SopClass.cs file.
		/// </summary>
		/// <param name="sopsFile"></param>
		public void WriteSopClasses(String sopsFile)
		{
			var writer = new StreamWriter(sopsFile);

			WriterHeader(writer);

			writer.WriteLine("    partial class SopClass");
			writer.WriteLine("    {");

			IEnumerator iter = _sopList.GetEnumerator();

			while (iter.MoveNext())
			{
				var sopClass = (SopClass) ((DictionaryEntry) iter.Current).Value;

				string category;
				GetSopClassDetails(sopClass, out category);

				writer.WriteLine("        /// <summary>");
				writer.WriteLine("        /// <para>" + sopClass.Name + "</para>");
				writer.WriteLine("        /// <para>UID: " + sopClass.Uid + "</para>");
				writer.WriteLine("        /// </summary>");
				writer.WriteLine("        public static readonly String " + sopClass.VarName + "Uid = \"" + sopClass.Uid + "\";");
				writer.WriteLine("");
				writer.WriteLine("        /// <summary>SopClass for");
				writer.WriteLine("        /// <para>" + sopClass.Name + "</para>");
				writer.WriteLine("        /// <para>UID: " + sopClass.Uid + "</para>");
				writer.WriteLine("        /// </summary>");
				writer.WriteLine("        public static readonly SopClass " + sopClass.VarName + " =");
				writer.WriteLine("                             new SopClass(\"" + sopClass.Name + "\", ");
				writer.WriteLine("                                          " + sopClass.VarName + "Uid, ");
				writer.WriteLine("                                          " + category + ");");
				writer.WriteLine("");
			}

			iter = _metaSopList.GetEnumerator();

			while (iter.MoveNext())
			{
				var sopClass = (SopClass) ((DictionaryEntry) iter.Current).Value;

				writer.WriteLine("        /// <summary>String UID for");
				writer.WriteLine("        /// <para>" + sopClass.Name + "</para>");
				writer.WriteLine("        /// <para>UID: " + sopClass.Uid + "</para>");
				writer.WriteLine("        /// </summary>");
				writer.WriteLine("        public static readonly String " + sopClass.VarName + "Uid = \"" + sopClass.Uid + "\";");
				writer.WriteLine("");
				writer.WriteLine("        /// <summary>SopClass for");
				writer.WriteLine("        /// <para>" + sopClass.Name + "</para>");
				writer.WriteLine("        /// <para>UID: " + sopClass.Uid + "</para>");
				writer.WriteLine("        /// </summary>");
				writer.WriteLine("        public static readonly SopClass " + sopClass.VarName + " =");
				writer.WriteLine("                             new SopClass(\"" + sopClass.Name + "\", ");
				writer.WriteLine("                                          " + sopClass.VarName + "Uid, ");
				writer.WriteLine("                                          SopClassCategory.Meta);");
				writer.WriteLine("");
			}

			writer.WriteLine("        static SopClass()");
			writer.WriteLine("        {");

			// Standard Sops
			iter = _sopList.GetEnumerator();

			while (iter.MoveNext())
			{
				var sopClass = (SopClass) ((DictionaryEntry) iter.Current).Value;

				writer.WriteLine("            _sopList.Add(" + sopClass.VarName + "Uid, ");
				writer.WriteLine("                         " + sopClass.VarName + ");");
				writer.WriteLine("");
			}

			// Now, Meta Sops
			iter = _metaSopList.GetEnumerator();

			while (iter.MoveNext())
			{
				var sopClass = (SopClass) ((DictionaryEntry) iter.Current).Value;

				writer.WriteLine("            _sopList.Add(" + sopClass.VarName + "Uid, ");
				writer.WriteLine("                         " + sopClass.VarName + ");");
				writer.WriteLine("");
			}
			writer.WriteLine("        }");
			writer.WriteLine("");

			writer.WriteLine("    }");
			WriterFooter(writer);

			writer.Close();
		}
	}
}